{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# 量子金融应用：投资组合分散化\n",
    "\n",
    "<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 概览\n",
    "\n",
    "当前量子计算应用到金融问题上的解决方案通常可分为三类量子算法，即量子模拟，量子优化以及量子机器学习 [1,2]。许多的金融问题本质上是一个组合优化问题，解决这些问题的算法通常具有较高的时间复杂度，实现难度较大。得益于量子计算强大的计算性能，未来有望通过量子算法解决这些复杂问题。\n",
    "\n",
    "量桨的 Quantum Finance 模块主要讨论的是量子优化部分的内容，即如何通过一些量子算法解决实际金融应用中的优化问题。本文主要介绍如何使用量子算法求解被动投资管理中投资组合分散化问题。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 投资组合分散化问题\n",
    "\n",
    "普通用户受专业知识和市场经验的不足的限制，在实际的投资中偏向于被动投资策略。指数投资就是一种常见的被动投资例子，比如说投资者长期购买并持有标普 $500$ 指数（Standard & Poor’s $500$）。作为投资人，假如你不想投资已有的指数，那么你也可以自己创建特定的指数投资组合，在市场中挑选合适的股票加入到创建的指数投资组合中。\n",
    "\n",
    "分散化是投资组合中平衡风险和收益的一个重要方法。对投资组合分散化的一个具体描述如下：当前可投资的股票数量为 $n$，指数投资组合中包含的股票数量为 $K$，需要对这 $n$ 个股票进行聚类，根据相似性将可选的股票划分为 $K$ 个类别，再从每个类别中选出最能代表该类别的股票，将其加入到指数组合中来，便于更好的控制风险，提高收益。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 编码投资组合分散化问题\n",
    "\n",
    "为了将投资组合分散化问题转化成一个参数化量子电路（parameterized quantum circuits, PQC）可解的问题，我们首先需要编码该问题的哈密顿量。\n",
    "\n",
    "在对该问题进行建模时，需要明确的有两个问题，第一是如何对不同的股票进行分类，第二便是以什么样的标准挑选代表性的股票。为了解决这两个问题，首先需要定义股票 $i$ 和股票 $j$ 之间的相似度 $\\rho_{ij}$:\n",
    "* $\\rho_{ii} = 1 \\quad $ 该股票和其自身的相似度为1\n",
    "* $\\rho_{ij} \\leq 1 \\quad$  不同股票间 $\\rho_{ij}$ 越大，相似度越高\n",
    "\n",
    "由于两股票间收益率的相关性，我们可以在协方差矩阵基础上进一步对时间序列间的相似性进行度量。动态时间规整（Dynamic Time Warping, DTW）是一种常见的衡量两个时间序列之间相似度的方法，在本文中，采用DTW算法来计算两股票之间的相似性。基于该度量，我们可以对股票进行分类并挑选代表性股票。对于给定的 $n$ 支股票，每支股票我们可以定义 $n$ 个二进制变量 $x_{ij}$ 和 $1$ 个二进制变量 $y_j$。对于变量 $x_{ij}$，每 $n$ 位一组，$i$ 表示是第几支股票，$j$ 表示在该股票对应的 $n$ 个二进制变量中的序号。每支股票的 $n$ 位二进制变量如果相同位置为 $1$ (即 $j$ 相同)，则说明这两只股票被分为同一类，其中 $i = j$ 的就是该类别中被选到指数组合中的最具代表性的股票：\n",
    "\n",
    "$$\n",
    "x_{ij}=\n",
    "\\begin{cases}\n",
    "1, & \\text{指数组合中的股票 $j$ 和股票 $i$ 具有最高的相似度}\\\\\n",
    "0, & \\text{其他情况}\n",
    "\\end{cases},\n",
    "$$\n",
    "\n",
    "$$\n",
    "y_{j}=\n",
    "\\begin{cases}\n",
    "1, & \\text{某类中的代表性股票 $j$ 被选择到指数组合中}\\\\\n",
    "0, & \\text{其他情况}\n",
    "\\end{cases}.\n",
    "$$\n",
    "\n",
    "在该问题中我们的模型便可以写作：\n",
    "\n",
    "$$\n",
    "\\mathcal{M}= \\max_{x_{ij}}\\sum_{i=1}^n\\sum_{j=1}^n \\rho_{ij}x_{ij}. \\tag{1}\n",
    "$$\n",
    "\n",
    "该模型需要满足以下几类约束：\n",
    "* 聚类约束：限制指数组合中只能有 $K$ 支股票\n",
    "    - $ \\sum_{j=1}^n y_j = K$\n",
    "* 整数约束：限制一只股票要么是在指数组合中，要么就不在\n",
    "     - $ x_{ij},y_j\\in{\\{0,1\\}}, \\forall i = 1, \\dots,n; j = 1, \\dots, n$\n",
    "* 一致性约束：保证如果一只股票可以代表另一支股票，那么它必须在指数组合中\n",
    "    - $\\sum_{j=1}^n x_{ij} = 1, \\forall i = 1,\\dots,n$\n",
    "    - $x_{ij} \\leq y_j, \\forall i = 1,\\dots,n; j = 1,\\dots, n$\n",
    "    - $x_{jj} = y_j, \\forall j = 1,\\dots,n$\n",
    "\n",
    "该模型目标就是让可选择的 $n$ 个股票与挑选的指数股票组合间相似性最大化。\n",
    "\n",
    "由于要对代价函数做梯度下降优化，所以在定义时就根据模型方程和相应的约束条件做一定修改：\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "C_x &= -\\sum_{i=1}^{n}\\sum_{j=1}^{n}\\rho_{ij}x_{ij} + A\\left(K- \\sum_{j=1}^n y_j \\right)^2 + \\sum_{i=1}^n A\\left(\\sum_{j=1}^n 1- x_{ij} \\right)^2 \\\\\n",
    "&\\quad + \\sum_{j=1}^n A\\left(x_{jj} - y_j\\right)^2 + \\sum_{i=1}^n \\sum_{j=1}^n A\\left(x_{ij}(1 - y_j)\\right).\\\\ \n",
    "\\end{aligned} \\tag{2}\n",
    "$$ \n",
    "\n",
    "该式子中第一项为相似性最大化，后面四项均为约束条件，$A$ 为惩罚参数，通常设置为较大的数字，使得最终表示指数投资组合结果的二进制字符串满足约束条件。\n",
    "\n",
    "现在我们需要将代价函数转为一个哈密顿量，从而完成投资组合分散化问题的编码。每一个二进制变量可以取0和1两个值，分别对应量子态 $|0\\rangle$ 和 $|1\\rangle$。每个二进制变量都对应一个量子比特，所以我们需要 $n^2 + n$ 个量子比特来解决投资组合分散化问题。因为我们的变量 $x_{ij}$ 的值为 $0$ 和 $1$，所以我们要构造一个本征值和它对应的哈密顿量。泡利 $Z$ 的本征值为 $\\pm 1$，于是我们构造的哈密顿量为 $\\frac{I-Z}{2}$，对应的本征值即为 $0$ 和 $1$。\n",
    "\n",
    "我们现在将二进制变量映射到泡利 $Z$ 矩阵上，从而使 $C_x$ 转化成哈密顿矩阵：\n",
    "\n",
    "$$\n",
    "x_{ij} \\mapsto \\frac{I-Z_{ij}}{2}, \\tag{3}\n",
    "$$\n",
    "\n",
    "这里 $Z_{ij} = I \\otimes I \\otimes \\ldots \\otimes Z \\otimes \\ldots \\otimes I$，也就是说 $Z$ 作用在 $ij$ 的量子比特上。通过这个映射，如果一个编号为 $ij$ 的量子比特的量子态为 $|1\\rangle$，那么对应的二进制变量的取值为 $x_{ij} |1\\rangle = \\frac{I-Z_{ij}}{2} |1\\rangle = 1|1\\rangle $，也就是说该项目是我们要投资的。同样地，对于量子态为 $|0\\rangle$的量子比特 $i$，它所对应的二进制变量的取值为 $x_{ij}|0\\rangle  = \\frac{I-Z_{ij}}{2} |0\\rangle = 0 |0\\rangle $。\n",
    "\n",
    "我们用上述映射将 $C_x$ 转化成量子比特数为 $n^2+n$ 的系统的哈密顿矩阵 $H_C$（其中 $x_{ij}$ 占 $n^2$ 个qubit，$y_j$ 占 $n$ 个 qubit），从而实现了投资组合分散化问题的量子化。这个哈密顿矩阵 $H_C$ 的基态即为投资组合分散化问题的最优解。在接下来的部分，我们将展示如何用参数化量子电路找到这个矩阵的基态，也就是对应最小本征值的本征态。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Paddle Quantum 实现\n",
    "\n",
    "要在量桨上实现用参数化量子电路解决量子金融中的投资组合分散化问题，首先要做的便是加载需要用到的包。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "source": [
    "#加载额外需要的包\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import datetime\n",
    "\n",
    "#加载飞桨，量桨相关的模块\n",
    "import paddle\n",
    "from paddle_quantum.circuit import UAnsatz\n",
    "from paddle_quantum.finance import DataSimulator\n",
    "from paddle_quantum.finance import portfolio_diversification_hamiltonian"
   ],
   "outputs": [],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-05-17T08:00:15.901429Z",
     "start_time": "2021-05-17T08:00:12.708945Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 准备实验数据\n",
    "\n",
    "和投资组合优化问题相似，在本问题中，我们选定的投资项目类型为股票。对于实验测试要用的数据，提供了两种方法：\n",
    "* 第一种是根据一定的条件，随机生成数据。\n",
    "\n",
    "如果采用这种方法准备实验数据，用户在初始化数据时，就需要给出可投资股票的名字列表，交易数据的开始日期和结束日期。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "source": [
    "num_assets = 3 #可选择股票数目\n",
    "stocks = [(\"TICKER%s\" % i) for i in range(num_assets)]\n",
    "data = DataSimulator( stocks = stocks, start = datetime.datetime(2016, 1, 1), end = datetime.datetime(2016, 1, 30))  \n",
    "data.randomly_generate() # 随机生成实验数据"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "* 第二种是用户可以选择读取本地收集到的真实数据集用于实验。考虑到文件中包含的股票数可能会很多，用户可以指定用于该实验的股票数量，即 上面初始化的`num_assets`。\n",
    "\n",
    "我们收集了 $12$ 支股票 $35$ 个交易日的收盘价格存放到 `realStockData_12.csv` 文件中，在这里我们只选择读取前 $3$ 个股票的信息。\n",
    "\n",
    "在本教程中，我们选择读取的真实数据作为实验数据。"
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-05-17T08:00:16.212260Z",
     "start_time": "2021-05-17T08:00:15.918792Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "source": [
    "df = pd.read_csv('realStockData_12.csv')\n",
    "dt = []\n",
    "for i in range(num_assets):\n",
    "    mylist = df['closePrice'+str(i)].tolist()\n",
    "    dt.append(mylist)\n",
    "print(dt) # 输出从文件中读取的3个股票在35个交易日中的收盘价格\n",
    "\n",
    "data.set_data(dt) # 指定实验数据为用户读取的数据"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "[[16.87, 17.18, 17.07, 17.15, 16.66, 16.79, 16.69, 16.99, 16.76, 16.52, 16.33, 16.39, 16.45, 16.0, 16.09, 15.54, 13.99, 14.6, 14.63, 14.77, 14.62, 14.5, 14.79, 14.77, 14.65, 15.03, 15.37, 15.2, 15.24, 15.59, 15.58, 15.23, 15.04, 14.99, 15.11, 14.5], [32.56, 32.05, 31.51, 31.76, 31.68, 32.2, 31.46, 31.68, 31.39, 30.49, 30.53, 30.46, 29.87, 29.21, 30.11, 28.98, 26.63, 27.62, 27.64, 27.9, 27.5, 28.67, 29.08, 29.08, 29.95, 30.8, 30.42, 29.7, 29.65, 29.85, 29.25, 28.9, 29.33, 30.11, 29.67, 29.59], [5.4, 5.48, 5.46, 5.49, 5.39, 5.47, 5.46, 5.53, 5.5, 5.47, 5.39, 5.35, 5.37, 5.24, 5.26, 5.08, 4.57, 4.44, 4.5, 4.56, 4.52, 4.59, 4.66, 4.67, 4.66, 4.72, 4.84, 4.81, 4.84, 4.88, 4.89, 4.82, 4.74, 4.84, 4.79, 4.63]]\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 编码哈密顿量\n",
    "\n",
    "这里我们将式（2）中的二进制变量用式（3）替换，从而构建哈密顿量 $H_C$。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "在编码哈密顿量的过程中，首先需要计算各股票之间的相似矩阵 $\\rho$。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "source": [
    "rho = data.get_similarity_matrix()"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "根据计算的相似矩阵和给定的参数构建哈密顿量。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "source": [
    "q = 2 # 指数组合中需要的股票数目\n",
    "penalty = num_assets # 惩罚参数：不小于可投资股票的数目\n",
    "hamiltonian = portfolio_diversification_hamiltonian(penalty, rho, q)"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 计算损失函数\n",
    "\n",
    "调用量桨内置的 [`complex entangled layer`](https://qml.baidu.com/api/paddle_quantum.circuit.uansatz.html) 构造参数化量子电路。该电路会返回一个输出态 $|\\vec{\\theta}\\rangle$，由此输出态，我们可以定义投资组合分散化问题在经典-量子混合模型下的损失函数：\n",
    "\n",
    "$$\n",
    "L(\\vec{\\theta}) =  \\langle\\vec{\\theta}|H_C|\\vec{\\theta}\\rangle.\n",
    "\\tag{4}\n",
    "$$\n",
    "\n",
    "之后我们利用经典的优化算法寻找最优参数 $\\vec{\\theta}^*$。下面的代码给出了通过量桨和飞桨搭建网络的过程。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "source": [
    "class PDNet(paddle.nn.Layer):\n",
    "\n",
    "    def __init__(self, n, p, dtype=\"float64\"):\n",
    "        super(PDNet, self).__init__()\n",
    "\n",
    "        self.p = p\n",
    "        self.num_qubits = n * (n+1)\n",
    "        self.theta = self.create_parameter(shape=[self.p, self.num_qubits, 3],\n",
    "            default_initializer=paddle.nn.initializer.Uniform(low=0, high=2 * np.pi),\n",
    "            dtype=dtype, is_bias=False)\n",
    "        # print(self.theta)\n",
    "\n",
    "    def forward(self, hamiltonian):\n",
    "        \"\"\"\n",
    "        前向传播\n",
    "        \"\"\"\n",
    "        cir = UAnsatz(self.num_qubits)\n",
    "        cir.complex_entangled_layer(self.theta, self.p)\n",
    "        cir.run_state_vector()\n",
    "        loss = cir.expecval(hamiltonian)\n",
    "\n",
    "        return loss, cir"
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 训练量子神经网络\n",
    "\n",
    "定义好了量子神经网络后，我们使用梯度下降的方法来更新其中的参数，使得式（4）的期望值最小。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "source": [
    "SEED = 1100   # 随机数种子\n",
    "p = 2       # 量子电路的层数\n",
    "ITR = 150    # 迭代次数\n",
    "LR = 0.4     # 梯度下降优化速率 "
   ],
   "outputs": [],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "使用飞桨，优化上面定义的网络。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "source": [
    "# 比特数量\n",
    "n = len(rho)\n",
    "\n",
    "# 固定随机数种子\n",
    "paddle.seed(SEED)\n",
    "\n",
    "# 定义量子神经网络\n",
    "net = PDNet(n, p)\n",
    "\n",
    "# 使用 Adam 优化器\n",
    "opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
    "\n",
    "# 梯度下降优化循环\n",
    "for itr in range(1, ITR + 1):\n",
    "    loss, cir = net(hamiltonian)\n",
    "    loss.backward()\n",
    "    opt.minimize(loss)\n",
    "    opt.clear_grad()\n",
    "    if itr % 10 == 0:\n",
    "        print(\"循环数:\", itr, \"    损失:\", \"%.4f\"% loss.numpy())"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "循环数: 10     损失: 7.7804\n",
      "循环数: 20     损失: 5.4414\n",
      "循环数: 30     损失: 3.6022\n",
      "循环数: 40     损失: 3.2910\n",
      "循环数: 50     损失: 1.9358\n",
      "循环数: 60     损失: 0.3872\n",
      "循环数: 70     损失: 0.1344\n",
      "循环数: 80     损失: 0.0774\n",
      "循环数: 90     损失: 0.0122\n",
      "循环数: 100     损失: 0.0068\n",
      "循环数: 110     损失: -0.0001\n",
      "循环数: 120     损失: -0.0019\n",
      "循环数: 130     损失: -0.0025\n",
      "循环数: 140     损失: -0.0028\n",
      "循环数: 150     损失: -0.0028\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 解码量子答案\n",
    "\n",
    "当调用优化器求得损失函数的最小值以及相对应的一组参数 $\\vec{\\theta}^*$后，为了进一步求得投资组合优化问题的近似解，需要从电路输出的量子态 $|\\vec{\\theta}^*\\rangle$ 中解码出经典优化问题的答案。物理上，解码量子态需要对量子态进行测量，然后统计测量结果的概率分布：\n",
    "\n",
    "$$\n",
    "p(z) = |\\langle z|\\vec{\\theta}^*\\rangle|^2.\n",
    "\\tag{6}\n",
    "$$\n",
    "\n",
    "在量子参数化电路表达能力足够的情况下，某个比特串出现的概率越大，意味着其是投资组合优化问题最优解的可能性越大。\n",
    "\n",
    "量桨提供了查看参数化量子电路输出状态的测量结果概率分布的函数。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "source": [
    "# 模拟重复测量电路输出态 2048 次\n",
    "prob_measure = cir.measure(shots=2048)\n",
    "investment = max(prob_measure, key=prob_measure.get)\n",
    "print(\"利用哈密顿量找到的解的比特串形式：\", investment)"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "利用哈密顿量找到的解的比特串形式： 100001001101\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "我们的测量结果是表示投资组合分散化问题答案的比特串：如上文结果 ``100001001101``，我们一共有 $n = 3$ 支可投资股票，选择两只到指数组合中。前 $n^2 = 9$ 位 ``100001001`` 代表 $x_{ij}$，每 $3$ 位为一组，第一组 ``100`` 中第一位为 $1$，代表它被划作一类。第二组 ``001`` 和第三组 ``001`` 中第三位被置为 $1$，代表它们被划为一类。同时，第一组和第三组 $1$ 出现的位置符合 $i = j$，即这两支股票为最能代表各自类的股票。另外，可以看出 $1$ 出现的位置是 $j = 1$ 和 $j = 3$，即两个位置可能为 $1$，这和我们预设的指数组合中有两只股票是对应的。同时，后 $3$ 位为 ``101``，代表 $y_j$, 表示第一支股票和第三支股票被选中放入指数组合中。通过上述说明，可以看出我们求解得到的结果是一个有效解。如果最后的结果不是上述这种有效解，读者依然可以通过调整参数化量子电路的参数值，来获得更好的训练效果。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 结语\n",
    "\n",
    "在本教程中，我们主要讨论了分散化投资中如何对可投资项目进行分类，以及如何挑选具有代表性的到我们的投资组合中来。在本问题中，每个投资项目都需要 $n$ 位量子比特来表示分类，$1$ 位量子比特表示是否被选中。受量子比特数目的限制，目前能够处理的投资项目数还比较少。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "_______\n",
    "\n",
    "## 参考文献\n",
    "\n",
    "[1] Orus, Roman, Samuel Mugel, and Enrique Lizaso. \"Quantum computing for finance: Overview and prospects.\" [Reviews in Physics 4 (2019): 100028.](https://arxiv.org/abs/1807.03890)\n",
    "\n",
    "[2] Egger, Daniel J., et al. \"Quantum computing for Finance: state of the art and future prospects.\" [IEEE Transactions on Quantum Engineering (2020).](https://arxiv.org/abs/2006.14510)"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "3b61f83e8397e1c9fcea57a3d9915794102e67724879b24295f8014f41a14d85"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.10"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}